package com.partsoft.webs.zks;

import java.util.*;
import java.util.Map.Entry;

import org.zkoss.zk.ui.*;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zkplus.databind.DataBinder;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class AnnotateDataBinder extends DataBinder {

	private static final long serialVersionUID = 8183335789960722526L;

	public static final String ARG_DATABINDER_REGISTED = "__databinder_registed__";

	public AnnotateDataBinder() {
	}

	public AnnotateDataBinder(Desktop desktop) {
		init(desktop, true);
	}

	public AnnotateDataBinder(Page page) {
		init(page, true);
	}

	public AnnotateDataBinder(Component comp) {
		init(comp, true);
	}

	public AnnotateDataBinder(Component comps[]) {
		init(comps, true);
	}

	public AnnotateDataBinder(Desktop desktop, boolean defaultConfig) {
		init(desktop, defaultConfig);
	}

	public AnnotateDataBinder(Page page, boolean defaultConfig) {
		init(page, defaultConfig);
	}

	public AnnotateDataBinder(Component comps[], boolean defaultConfig) {
		init(comps, defaultConfig);
	}

	public AnnotateDataBinder(Component comp, boolean defaultConfig) {
		init(comp, defaultConfig);
	}

	public void init(Desktop desktop, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (Iterator it = desktop.getComponents().iterator(); it.hasNext(); ) {
			loadAnnotations((Component) it.next());
		}
	}

	public void init(Page page, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (Iterator it = page.getRoots().iterator(); it.hasNext(); )
			loadAnnotations((Component) it.next());
	}

	public void init(Component comps[], boolean defaultConfig) {
		setDefaultConfig(defaultConfig);

		for (int j = 0; j < comps.length; ++j) {
			if (comps[j].getAttribute(ARG_DATABINDER_REGISTED) != null)
				continue;
			final List children = comps[j].getChildren();
			for (final Iterator it = children.iterator(); it.hasNext();) {
				loadAnnotations((Component) it.next()); // recursive back
			}
			comps[j].setAttribute(ARG_DATABINDER_REGISTED, true);
		}
	}

	public void init(Component comp, boolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		if (comp.getAttribute(ARG_DATABINDER_REGISTED) != null)
			return;
		final List children = comp.getChildren();
		for (final Iterator it = children.iterator(); it.hasNext();) {
			loadAnnotations((Component) it.next());
		}
		comp.setAttribute(ARG_DATABINDER_REGISTED, true);
	}

	private void loadAnnotations(Component comp) {
		loadComponentAnnotation(comp);
		loadComponentPropertyAnnotation(comp);
		List children = comp.getChildren();
		for (Iterator it = children.iterator(); it.hasNext(); loadAnnotations((Component) it.next()))
			;
	}

	private void loadComponentPropertyAnnotation(Component comp) {
		loadComponentPropertyAnnotationByAnnotName(comp, "default");
		loadComponentPropertyAnnotationByAnnotName(comp, "bind");
	}

	private void loadComponentPropertyAnnotationByAnnotName(Component comp, String annotName) {
		ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final List props = compCtrl.getAnnotatedPropertiesBy(annotName);
		for (final Iterator it = props.iterator(); it.hasNext(); ) {
			final String propName = (String) it.next();
			//[0] value, [1] loadWhenEvents, [2] saveWhenEvents, [3] access, [4] converter, [5] args, [6] loadAfterEvents, [7] saveAfterEvents
			final Object[] objs = loadPropertyAnnotation(comp, propName, annotName);
			addBinding(comp, propName, (String) objs[0], 
					(List) objs[1], (List) objs[2], (String) objs[3], (String) objs[4], (Map) objs[5], (List) objs[6], (List) objs[7]);
		}
	}
	

	private void loadComponentAnnotation(Component comp) {
		loadComponentAnnotation(comp, "default");
		loadComponentAnnotation(comp, "bind");
	}

	private void loadComponentAnnotation(Component comp, String annotName) {
		ComponentCtrl compCtrl = (ComponentCtrl) comp;
		Annotation ann = compCtrl.getAnnotation(null, annotName);
		if (ann != null) {
			Map<String, String[]> attrs = ann.getAttributes();
			for(Entry<String, String[]> me: attrs.entrySet()) {
				String attr = me.getKey();
				String[] val = me.getValue();
				if (val.length != 1)
					throw new UiException("Not support array value, "+annotName);
				//[0] bean value, [1 ~ *] tag:expression
				List expr = parseExpression(val[0] , ";");
				if (expr == null || expr.get(0) == null) {
					throw new UiException("Cannot find any bean value in the annotation <a:bind "+attr+"=\"\"/> for component "+comp+", id="+comp.getId());
				} else {
					List tags = parseExpression((String)expr.get(0), ":");
					if (tags.size() > 1) {
						throw new UiException("bean value must be defined as the first statement in the annotation <a:bind "+attr+"=\"\"/> for component "+comp+", id="+comp.getId());
					}
				}
				
				List<String> loadWhenEvents = null;
				List<String> saveWhenEvents = null;
				List<String> loadAfterEvents = null;
				List<String> saveAfterEvents = null;
				String access = null;
				String converter = null;
				Map<Object, Object> args = null;
				
				//process tags
				for(int j = 1; j < expr.size(); ++j) {
					List tags = parseExpression((String)expr.get(j), ":");
					if (tags == null) {
						continue; //skip
					}
					if ("converter".equals(tags.get(0))) {
						converter = tags.size() > 1 ? (String) tags.get(1) : NULLIFY;
					} else if ("save-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							saveWhenEvents = parseExpression((String)tags.get(1), ",");
						} else {
							saveWhenEvents.add(NULLIFY);
						}
					} else if ("load-after".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							loadAfterEvents = parseExpression((String)tags.get(1), ",");
						} else {
							loadAfterEvents.add(NULLIFY);
						}
					} else if ("load-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							loadWhenEvents = parseExpression((String)tags.get(1), ",");
						} else {
							loadWhenEvents.add(NULLIFY);
						}
					} else if ("access".equals(tags.get(0))) {
						access = tags.size() > 1 ? (String) tags.get(1) : NULLIFY;
					} else if ("save-after".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							saveAfterEvents = parseExpression((String)tags.get(1), ",");
						} else {
							saveAfterEvents.add(NULLIFY);
						}
					} else {
						if (args == null) {
							args = new HashMap<Object, Object>(1);
						}
						args.put(tags.get(0), tags.get(1));
					}
				}
				
				if (loadWhenEvents != null && loadWhenEvents.isEmpty()) {
					loadWhenEvents = null;
				}
				if (saveWhenEvents != null && saveWhenEvents.isEmpty()) {
					saveWhenEvents = null;
				}
				if (loadAfterEvents != null && loadAfterEvents.isEmpty()) {
					loadAfterEvents = null;
				}
				if (saveAfterEvents != null && saveAfterEvents.isEmpty()) {
					saveAfterEvents = null;
				}
				
				addBinding(comp, attr, (String) expr.get(0), loadWhenEvents, saveWhenEvents, access, converter, args, loadAfterEvents, saveAfterEvents);
			}
		}
	}

	static List<String> parseExpression(String expr, String separator) {
		if (expr == null) {
			return null;
		}
		List<String> results = new ArrayList<String>(6);
		while(true) {
			int j = expr.indexOf(separator);
			if (j < 0) {
				results.add(expr.trim());
				return results;
			}
			results.add(expr.substring(0, j).trim());

			if (expr.length() <= (j+1)) {
				return results;
			}
			expr = expr.substring(j+1);
		}
	}

}
